#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UTable.Input.MultiTouch;
using System.Windows;
using UTable.Objects.Controls;
using System.Windows.Threading;

namespace UTable.Objects.Handlers
{
	public class SingleFingerGestureHandler: SingleFingerHandler
	{
		#region Private Fields

		Point _originalFingerPosition;

		Point _originalDragPosition;

        long _FingerDownTimeTick;

        Point _previousFingerPosition;

        UIElement _relativeElement;

        DispatcherTimer _holdTimer;

		PhysicsTracker _physicsTracker = new PhysicsTracker();

		#endregion

        #region Private Methods

        void _holdTimer_Tick(object sender, EventArgs e)
        {
            if (_holdTimer != null)
            {
                _holdTimer.Stop();
				_holdTimer = null;

                if (GestureDetected != null && this.EnabledGestures.Contains(SingleFingerGestureType.Holding))
                    GestureDetected(this, new SingleFingerGestureEventArgs(SingleFingerGestureType.Holding, _previousFingerPosition));
            }
        }

        #endregion

        #region Properties

        public double MaxTapDistance { get; set; }

        public SingleFingerStateType State { get; set; }

		public bool TrackVelocity
		{ 
			get { return _physicsTracker.TrackVelocity; }
			set { _physicsTracker.TrackVelocity = value; } 
		}

		public TimeSpan MinHoldTime { get; set; }

		public List<SingleFingerGestureType> EnabledGestures { get; set; }

		#endregion

		#region Constructors

		public SingleFingerGestureHandler(IControl node, UIElement relativeElement)
			: base(node)
		{
			_relativeElement = relativeElement;
			State = SingleFingerStateType.Hold;
            CaptureFinger = false;

			// initialize the variables
			EnabledGestures = new List<SingleFingerGestureType>();
			EnabledGestures.Add(SingleFingerGestureType.Tap);
			EnabledGestures.Add(SingleFingerGestureType.DragStarted);
			EnabledGestures.Add(SingleFingerGestureType.Dragging);
			EnabledGestures.Add(SingleFingerGestureType.DragEnded);
            MaxTapDistance = 20;
			TrackVelocity = false;
			MinHoldTime = TimeSpan.FromMilliseconds(2000);
		}

		#endregion

		#region Protected Methods

		protected override void OnSingleFingerDown(FingerEventArgs fArgs)
		{
            Point p = (Node as UIElement).TranslatePoint(fArgs.Position, _relativeElement);
            _originalFingerPosition = _previousFingerPosition = p;
            _FingerDownTimeTick = fArgs.Timestamp;
            State = SingleFingerStateType.Hold;

            // start the hold timer
            if (this.EnabledGestures.Contains(SingleFingerGestureType.Holding))
            {
                _holdTimer = new DispatcherTimer();
                _holdTimer.Tick += new EventHandler(_holdTimer_Tick);
                _holdTimer.Interval = MinHoldTime;
                _holdTimer.Start();
            }
		}

		protected override void OnSingleFingerMove(FingerEventArgs fArgs)
		{
            Point p = (Node as UIElement).TranslatePoint(fArgs.Position, _relativeElement);
            if (State == SingleFingerStateType.Hold)
            {
                if ((fArgs.Position - this._originalFingerPosition).Length > MaxTapDistance)
                {
                    // Begin Dragged
                    // try to capture the finger
                    if (Node.CaptureFinger(fArgs.FingerID))
                    {
                        this.State = SingleFingerStateType.Drag;
                        _originalDragPosition = _previousFingerPosition;
                        if (GestureDetected != null && this.EnabledGestures.Contains(SingleFingerGestureType.DragStarted))
							GestureDetected(this, new SingleFingerGestureDragEventArgs(SingleFingerGestureType.DragStarted, p, _previousFingerPosition, _originalDragPosition, _physicsTracker.UpdateVelocity(p, fArgs.Timestamp)));

                        // stop holding if needed
                        if (_holdTimer != null)
                        {
                            _holdTimer.Stop();
                            _holdTimer = null;
                        }
                    }
                }
            }
            else if (State == SingleFingerStateType.Drag)
            {
                if (GestureDetected != null && this.EnabledGestures.Contains(SingleFingerGestureType.Dragging))
                {
                    GestureDetected(this, new SingleFingerGestureDragEventArgs(SingleFingerGestureType.Dragging, p, _previousFingerPosition, _originalDragPosition, _physicsTracker.UpdateVelocity(p, fArgs.Timestamp)));
                }
            }
            _previousFingerPosition = p;
		}

		protected override void OnSingleFingerUp(FingerEventArgs fArgs)
		{
            Point p = (Node as UIElement).TranslatePoint(fArgs.Position, _relativeElement);

            // stop holding if needed
            if (_holdTimer != null)
            {
                _holdTimer.Stop();
                _holdTimer = null;
            }

            // raise click event if needed
            if (EnabledGestures.Contains(SingleFingerGestureType.Click))
            {
                if (Node is FrameworkElement)
                {
                    FrameworkElement element = Node as FrameworkElement;
                    if (fArgs.Position.X >= 0 && fArgs.Position.Y >= 0 && fArgs.Position.X <= element.ActualWidth && fArgs.Position.Y <= element.ActualHeight)
                    {
                        if (GestureDetected != null)
                            GestureDetected(this, new SingleFingerGestureEventArgs(SingleFingerGestureType.Click, p));
                    }
                }
            }


            if (State == SingleFingerStateType.Hold)
            {
                // raise tap event
                if (GestureDetected != null && this.EnabledGestures.Contains(SingleFingerGestureType.Tap))
                    GestureDetected(this, new SingleFingerGestureTapEventArgs(p));
            }
            else if (State == SingleFingerStateType.Drag)
            {
                // release the finger capture
                Node.UncaptureFinger(fArgs.FingerID);

                // release drag event
				if (GestureDetected != null && this.EnabledGestures.Contains(SingleFingerGestureType.DragEnded))
					GestureDetected(this, new SingleFingerGestureDragEventArgs(SingleFingerGestureType.DragEnded, p, _previousFingerPosition, _originalDragPosition, _physicsTracker.UpdateVelocity(p, fArgs.Timestamp)));
            }
		}

		#endregion

		#region Events

		public event SingleFingerGestureEventHandler GestureDetected;

		#endregion
	}

    public enum SingleFingerStateType
    {
        Hold,
        Drag,
    }

    public enum SingleFingerGestureType
	{
		/// <summary>
		/// Raised when one finger down, and up without 
		/// moving exceeding <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MaxTapDistance"/>
		/// </summary>
		Tap,
		/// <summary>
		/// Raised when one finger down, holding for <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MinHoldTime"/> without 
		/// moving exceeding <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MaxTapDistance"/>
		/// </summary>
        Holding,
		/// <summary>
		/// Raised when one finger down, and up within the bound 
		/// of <see cref="UTable.Objects.Handlers.SingleFingerHandler.Node"/>
		/// </summary>
        Click,
		/// <summary>
		/// Raised when one finger down, and move exceeding <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MaxTapDistance"/>
		/// </summary>
		DragStarted,
		/// <summary>
		/// Raised when one finger move after the DragStarted event is raised
		/// </summary>
		Dragging,
		/// <summary>
		/// Raised when one finger up after the DragStarted event is raised
		/// </summary>
		DragEnded,
	}

	public class SingleFingerGestureEventArgs
	{
		public SingleFingerGestureEventArgs(SingleFingerGestureType type, Point fingerPosition)
		{
			Type = type;
			FingerPosition = fingerPosition;
		}

		public SingleFingerGestureType Type { get; protected set; }

		public Point FingerPosition { get; protected set; }
	}

	public class SingleFingerGestureTapEventArgs: SingleFingerGestureEventArgs
	{
		public SingleFingerGestureTapEventArgs(Point fingerPosition)
			: base(SingleFingerGestureType.Tap, fingerPosition)
		{
		}
	}

	public class SingleFingerGestureDragEventArgs : SingleFingerGestureEventArgs
	{
		public Point OriginalFingerPosition { get; protected set; }

		public Point PreviousFingerPosition { get; protected set; }

        public Vector Velocity { get; protected set; }

		public SingleFingerGestureDragEventArgs(SingleFingerGestureType type, Point fingerPosition, Point previousPosition, Point originalPosition)
			:base (type, fingerPosition)
		{
            OriginalFingerPosition = originalPosition;
			PreviousFingerPosition = previousPosition;
            Velocity = new Vector(double.NaN ,double.NaN);
		}

        public SingleFingerGestureDragEventArgs(SingleFingerGestureType type, Point fingerPosition, Point previousPosition, Point originalPosition, Vector velocity)
            : base(type, fingerPosition)
        {
            OriginalFingerPosition = originalPosition;
            PreviousFingerPosition = previousPosition;
            Velocity = velocity;
        }
	}

    public delegate void SingleFingerGestureEventHandler(SingleFingerGestureHandler sender, SingleFingerGestureEventArgs args);
}
